import { Format } from './format.js'
import { Has } from './has.js'

/**
 * The loosed and most flexibly magical matching strategy.
 *
 * - If the objects pass the {@link tcompare!has.Has} test, then return true.
 * - If the pattern is a regular expression, then test it against the
 *   string form of the test value.
 * - If the pattern is a string, and the test value is a string, then test
 *   that the pattern appears somewhere in the test value.
 * - If the pattern is a string, and the test value is a Date, then test
 *   parse the pattern as a Date and verify that they have the same value
 * - If the pattern is a constructor, then test that the value is an
 *   instance of the constructor. In the case of scalar constructors, like
 *   Number, Boolean, etc, also pass if the `typeof` the value matches.
 *   That is `Match(1, { expect: Number })` passes.
 * - If the pattern is the `Array` constructor, then pass for any iterable
 *   valuef.
 */

export class Match extends Has {
  test(): boolean | 'COMPLEX' {
    const obj = this.object
    const pattern = this.expect

    return super.test() === true
      ? true
      : // failures that would also fail in the super class
      // but if they didn't pass, then should fail here, too
      pattern == null || obj == null
      ? false
      : pattern instanceof RegExp && obj instanceof RegExp
      ? false
      : Buffer.isBuffer(obj) && Buffer.isBuffer(pattern)
      ? false
      : typeof pattern === 'symbol'
      ? false
      : // ok, Match-specific stuff
      pattern instanceof RegExp
      ? pattern.test('' + obj)
      : typeof obj === 'string' &&
        typeof pattern === 'string' &&
        pattern
      ? obj.indexOf(pattern) !== -1
      : obj instanceof Date && typeof pattern === 'string'
      ? obj.getTime() === new Date(pattern).getTime()
      : pattern === BigInt
      ? typeof obj === 'bigint'
      : pattern === Buffer
      ? Buffer.isBuffer(obj)
      : pattern === Function
      ? typeof obj === 'function'
      : pattern === Number
      ? typeof obj === 'number' && obj === obj && isFinite(obj)
      : pattern === String
      ? typeof obj === 'string'
      : pattern === Symbol
      ? typeof obj === 'symbol'
      : pattern === Boolean
      ? typeof obj === 'boolean'
      : pattern === Map
      ? this.isMap()
      : pattern === Set
      ? this.isSet()
      : pattern === Object
      ? obj && typeof obj === 'object'
      : pattern === Array
      ? new Format(obj).isArray()
      : !this.isError() && pattern instanceof Error
      ? false
      : this.isError() &&
        ((pattern.message &&
          !new Match(obj.message, {
            expect: pattern.message,
          }).test()) ||
          (pattern.name &&
            !new Match(obj.name, {
              expect: pattern.name,
            }).test()))
      ? false
      : // standard deep matching stuff, same as parent, but not simple.
      this.isSet() && !(pattern instanceof Set)
      ? false
      : this.isMap() && !(pattern instanceof Map)
      ? false
      : typeof pattern === 'function' && typeof obj === 'object'
      ? obj instanceof pattern
      : typeof obj !== 'object' || typeof pattern !== 'object'
      ? false
      : 'COMPLEX'
  }
}
